﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.Contracts;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Security.Policy;
using System.Text;

namespace Derbster.Domain
{
    public enum LineupPosition
    {
        Blocker,
        Pivot,
        Jammer
    }

    public class Lineup : IEnumerable<Player>
    {
        private readonly CommandManager _commandManager;

        [ContractVerification(false)]
        internal Lineup()
        {
            
        }

        public Lineup(Jam jam)
        {
            Contract.Requires(jam != null);
            Jam = jam;
            Blockers = new HashSet<Player>();

            _commandManager = new CommandManager();
        }

        public Lineup(Jam jam, HashSet<Player> blockers)
        {
            Contract.Requires(jam != null);
            Contract.Requires(blockers != null && blockers.Count > 0 && blockers.Count <= 5);

            Jam = jam;
            Blockers = blockers;
            Pivot = null;
            Jammer = null;

            _commandManager = new CommandManager();
        }

        public Lineup(Jam jam, Player jammer, HashSet<Player> blockers)
        {
            Contract.Requires(jam != null);
            Contract.Requires(jammer != null);
            Contract.Requires(blockers != null && blockers.Count <= 4);
            Contract.Requires(!blockers.Contains(jammer));

            Jam = jam;
            Blockers = blockers;
            Pivot = null;
            Jammer = jammer;

            _commandManager = new CommandManager();
        }

        public Lineup(Jam jam, Player jammer, Player pivot, HashSet<Player> blockers)
        {
            Contract.Requires(jam != null);
            Contract.Requires(jammer != null);
            Contract.Requires(pivot != null);
            Contract.Requires(blockers != null && blockers.Count <= 3);
            Contract.Requires(!blockers.Contains(jammer));
            Contract.Requires(!blockers.Contains(pivot));

            Jam = jam;
            Blockers = blockers;
            Jammer = jammer;
            Pivot = pivot;

            _commandManager = new CommandManager();
        }

        [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int LineupId { get; internal set; }
        public Player Pivot { get; internal set; }
        public HashSet<Player> Blockers { get; internal set; }
        public Player Jammer { get; internal set; }
        public bool StarPassed { get; internal set; }

        [NotMapped]
        private Jam Jam { get; set; }

        [NotMapped]
        public int Count
        {
            get
            {
                return (Jammer==null ? 0 : 1)
                    + (Pivot == null ? 0 : 1) + Blockers.Count;
            }
        }

        public void AddJammer(Player player)
        {
            Contract.Requires(Jam != null);
            Contract.Requires(player != null);
            Contract.Requires(Jammer == null);
            Contract.Requires(Pivot != player);
            Contract.Requires(Blockers != null);
            Contract.Requires(Blockers.All(p=>p != player));
            Contract.Ensures(Jammer == player);
            Contract.Ensures(Pivot != player);
            Contract.Ensures(Blockers.All(p => p != player));

            this.DoAddJammer(player);
            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => DoRemoveFromLineup(player),
                    () => DoAddJammer(player));
            }
            Contract.Assert(!Blockers.Contains(player));
        }

        private void DoAddJammer(Player player)
        {
            Contract.Requires(Jam != null);
            Contract.Requires(player != null);
            Contract.Requires(Jammer == null);
            Contract.Requires(Pivot != player);
            Contract.Requires(Blockers != null);
            Contract.Requires(Blockers.All(p => p != player));
            Contract.Ensures(Jammer == player);
            Contract.Ensures(Pivot != player);
            Contract.Ensures(Blockers.All(p=>p != player));

            Jammer = player;
        }

        public void AddPivot(Player player)
        {
            Contract.Requires(Jam != null);
            Contract.Requires(player != null);
            Contract.Requires(Pivot == null);
            Contract.Requires(Jammer != player);
            Contract.Requires(Blockers != null);
            Contract.Requires(Blockers.All(p => p != player));
            Contract.Ensures(Pivot == player);
            Contract.Ensures(Jammer != player);
            Contract.Ensures(Blockers.All(p => p != player));

            this.DoAddPivot(player);
            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => DoRemoveFromLineup(player),
                    () => DoAddPivot(player));
            }
            Contract.Assert(!Blockers.Contains(player));
        }

        private void DoAddPivot(Player player)
        {
            Contract.Requires(Jam != null);
            Contract.Requires(player != null);
            Contract.Requires(Pivot == null);
            Contract.Requires(Jammer != player);
            Contract.Requires(Blockers != null);
            Contract.Requires(Blockers.All(p => p != player));
            Contract.Ensures(Pivot == player);
            Contract.Ensures(Jammer != player);
            Contract.Ensures(Blockers.All(p => p != player));

            Pivot = player;
        }

        public void AddBlocker(Player player)
        {
            Contract.Requires(Jam != null);
            Contract.Requires(player != null);
            Contract.Requires(Count < 5);
            Contract.Requires(Jammer != player);
            Contract.Requires(Pivot != player);
            Contract.Requires(Blockers != null);
            Contract.Requires(Blockers.All(p => p != player));
            Contract.Ensures(Jammer != player);
            Contract.Ensures(Pivot != player);
            Contract.Ensures(Blockers.Any(p => p == player));

            this.DoAddBlocker(player);
            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => this.DoRemoveFromLineup(player),
                    () => this.DoAddBlocker(player));
            }
        }

        private void DoAddBlocker(Player player)
        {
            Contract.Requires(Jam != null);
            Contract.Requires(player != null);
            Contract.Requires(Count < 5);
            Contract.Requires(Jammer != player);
            Contract.Requires(Pivot != player);
            Contract.Requires(Blockers != null);
            Contract.Requires(Blockers.All(p => p != player));
            Contract.Ensures(Jammer != player);
            Contract.Ensures(Pivot != player);
            Contract.Ensures(Blockers.Any(p => p == player));

            Blockers.Add(player);
        }


        public void CorrectLineup(Player oldPlayer, Player newPlayer)
        {
            Contract.Requires(Jam != null);
            Contract.Requires(oldPlayer != null);
            Contract.Requires(newPlayer != null);
            Contract.Requires(Blockers != null);
            Contract.Requires(Jammer == oldPlayer 
                || Pivot == oldPlayer 
                || Blockers.Any(p => p == oldPlayer));
            Contract.Requires(Jammer != newPlayer);
            Contract.Requires(Pivot != newPlayer);
            Contract.Requires(Blockers.All(p => p != newPlayer));
            Contract.Ensures(Jammer != oldPlayer);
            Contract.Ensures(Pivot != oldPlayer);
            Contract.Ensures(Blockers.All(p => p != oldPlayer));
            Contract.Ensures(Jammer == newPlayer 
                || Pivot == newPlayer 
                || Blockers.Any(p=>p == newPlayer));

            this.DoCorrectLineup(oldPlayer, newPlayer);

            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => this.DoCorrectLineup(newPlayer, oldPlayer),
                    () => this.DoCorrectLineup(oldPlayer, newPlayer));
            }
            Contract.Assert(!Blockers.Contains(oldPlayer));
        }

        private void DoCorrectLineup(Player oldPlayer, Player newPlayer)
        {
            Contract.Requires(Jam != null);
            Contract.Requires(oldPlayer != null);
            Contract.Requires(newPlayer != null);
            Contract.Requires(Blockers != null);
            Contract.Requires(Jammer == oldPlayer
                || Pivot == oldPlayer
                || Blockers.Any(p => p == oldPlayer));
            Contract.Requires(Jammer != newPlayer);
            Contract.Requires(Pivot != newPlayer);
            Contract.Requires(Blockers.All(p => p != newPlayer));
            Contract.Ensures(Jammer != oldPlayer);
            Contract.Ensures(Pivot != oldPlayer);
            Contract.Ensures(Blockers.All(p => p != oldPlayer));
            Contract.Ensures(Jammer == newPlayer 
                || Pivot == newPlayer
                || Blockers.Any(p => p == newPlayer));

            if (Jammer != null && Jammer == oldPlayer)
            {
                Jammer = newPlayer;
            }
            else if (Pivot != null && Pivot == oldPlayer)
            {
                
                Pivot = newPlayer;
            }
            else
            {
                Blockers.Remove(oldPlayer);
                Blockers.Add(newPlayer);
            }
            
            Contract.Assert(!Blockers.Contains(oldPlayer));
        }

        public void RemoveFromLineup(Player player)
        {
            Contract.Requires(Jam != null);
            Contract.Requires(player != null);
            Contract.Requires(Blockers != null);
            Contract.Requires(Jammer == player 
                || Pivot == player 
                || Blockers.Any(p => p==player));
            Contract.Ensures(Jammer != player);
            Contract.Ensures(Pivot != player);
            Contract.Ensures(Blockers.All(p => p != player));

            if (Jammer == player)
            {
                DoRemoveFromLineup(player);
                if (_commandManager != null)
                {
                    _commandManager.Add(
                        () => DoAddJammer(player),
                        () => DoRemoveFromLineup(player));
                }    
            }
            else if (Pivot == player)
            {
                DoRemoveFromLineup(player);
                if (_commandManager != null)
                {
                    _commandManager.Add(
                        () => DoAddPivot(player),
                        () => DoRemoveFromLineup(player));
                }
            }
            else
            {
                DoRemoveFromLineup(player);
                if (_commandManager != null)
                {
                    _commandManager.Add(
                        () => DoAddBlocker(player), 
                        () => DoRemoveFromLineup(player));
                }
            }
            Contract.Assert(!Blockers.Contains(player));
        }

        private void DoRemoveFromLineup(Player player)
        {
            Contract.Requires(Jam != null);
            Contract.Requires(player != null);
            Contract.Requires(Blockers != null);
            Contract.Requires(Jammer == player 
                || Pivot == player 
                || Blockers.Any(p => p== player));
            Contract.Ensures(Jammer != player);
            Contract.Ensures(Pivot != player);
            Contract.Ensures(Blockers.All(p => p != player));

            if (Jammer != null && Jammer == player)
            {
                Jammer = null;
            }
            else if (Pivot != null && Pivot == player)
            {
                Pivot = null;
            }
            else
            {
                Blockers.Remove(player);
            }
            Contract.Assert(!Blockers.Contains(player));
        }

        public bool VerifyLineup()
        {
            Contract.Requires(Blockers != null);
            if (Blockers.Contains(Jammer)) return false;
            if (Blockers.Contains(Pivot)) return false;
            if (Jammer == Pivot) return false;

            return true;

        }

        public IEnumerator<Player> GetEnumerator()
        {
            if (Jammer != null) yield return Jammer;
            if (Pivot != null) yield return Pivot;
            foreach (var blocker in Blockers)
            {
                yield return blocker;
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}
